﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Workflows.Components.TextMining.Clustering
{
    /// <summary>
    /// 
    /// </summary>
    public class CosineDistanceCalculator:IDistanceCalculator
    {
       
        #region IDistanceCalculator Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="termFreq1"></param>
        /// <param name="termFreq2"></param>
        /// <param name="contributingTerms"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, DocTermFreq> termFreq1,
            Dictionary<int, DocTermFreq> termFreq2,
            ref Dictionary<int, int> contributingTerms)
        {
            if (termFreq1 == null || termFreq2 == null || termFreq1.Count == 0 || termFreq2.Count == 0)
                return double.PositiveInfinity;

            Dictionary<int, int> unionTermIDs1 = new Dictionary<int, int>();
            Dictionary<int, int> unionTermIDs2 = new Dictionary<int, int>();
            foreach(int termID1 in termFreq1.Keys)
            {
                unionTermIDs1.Add(termID1,1);
                if(termFreq2.ContainsKey(termID1))
                {
                    unionTermIDs2.Add(termID1, termFreq1[termID1].Count);
                }
                else
                {
                    unionTermIDs2.Add(termID1,0);
                }
            }
            foreach(int termID2 in termFreq2.Keys)
            {
                if(!unionTermIDs2.ContainsKey(termID2))
                {
                    unionTermIDs2.Add(termID2,termFreq2[termID2].Count);
                }
                if(!unionTermIDs1.ContainsKey(termID2))
                {
                    unionTermIDs1.Add(termID2,0);
                }
            }

            double length1 = 0;
            double length2 = 0;

            //sum of vector multiplication
            double svMul = 0;

            foreach (int termID in unionTermIDs1.Keys)
            {
                double freq1 = unionTermIDs1[termID];
                length1 += Math.Pow(freq1, 2);
                double freq2 = unionTermIDs2[termID];
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if(freq1>0 && freq2>0)
                {
                    contributingTerms.Add(termID, Math.Min(termFreq1[termID].Count, termFreq2[termID].Count));
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);

            double contributingTermScale =
                TermWeightCalculator.TaminoCommonTermScale(
                    termFreq1.Count, termFreq2.Count,
                    contributingTerms.Count);
            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)contributingTerms.Count * 2 /
                                 (termFreq1.Count + termFreq2.Count - contributingTerms.Count);
            cosineDistance = cosineDistance*docLenScale;
            return cosineDistance*contributingTermScale;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="termFreq1"></param>
        /// <param name="termFreq2"></param>
        /// <param name="termWeights"></param>
        /// <param name="useNGramSimilarity"></param>
        /// <param name="contributingTerms"></param>
        /// <returns></returns>
        public double CalculateDistance(
            Dictionary<int, DocTermFreq> termFreq1,
            Dictionary<int, DocTermFreq> termFreq2,
            Dictionary<int, double> termWeights,
            bool useNGramSimilarity, int ngramLen,
            ref Dictionary<int, int> contributingTerms)
        {
            Dictionary<int, double> unionTermIDs1 = new Dictionary<int, double>();
            Dictionary<int, double> unionTermIDs2 = new Dictionary<int, double>();
            foreach (int termID1 in termFreq1.Keys)
            {
                unionTermIDs1.Add(termID1, termFreq1[termID1].Weight * termFreq1[termID1].Count);
                if (termFreq2.ContainsKey(termID1))
                {
                    unionTermIDs2.Add(termID1, termFreq2[termID1].Count * termFreq2[termID1].Weight);
                }
                else
                {
                    unionTermIDs2.Add(termID1, 0);
                }
            }
            foreach (int termID2 in termFreq2.Keys)
            {
                if (!unionTermIDs2.ContainsKey(termID2))
                {
                    unionTermIDs2.Add(termID2, termFreq2[termID2].Count * termFreq2[termID2].Weight);
                }
                if (!unionTermIDs1.ContainsKey(termID2))
                {
                    unionTermIDs1.Add(termID2, 0);
                }
            }

            double length1 = 0;
            double length2 = 0;
            Dictionary<int, Dictionary<int, double>> termSimilarityMatrix =
                TermSimilarityMatrix.BuildTermDistanceMatrixUsingNgram(
                    termFreq1, termFreq2);

            //sum of vector multiplication
            double svMul = 0;

            foreach (int termID in unionTermIDs1.Keys)
            {
                double freq1 = unionTermIDs1[termID];
                double freq2 = unionTermIDs2[termID];
                if (useNGramSimilarity && ngramLen>0)
                {
                    Dictionary<int, double> similarTerms = termSimilarityMatrix[termID];
                    foreach (int similarTermID in similarTerms.Keys)
                    {
                        if (unionTermIDs1[similarTermID] > freq1)
                            freq1 = unionTermIDs1[similarTermID];
                        if (unionTermIDs2[similarTermID] > freq2)
                            freq2 = unionTermIDs2[similarTermID];
                    }
                }

                length1 += Math.Pow(freq1, 2);
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if (freq1 > 0 && freq2 > 0)
                {
                    int count = 0;
                    if (termFreq1.ContainsKey(termID) && termFreq2.ContainsKey(termID))
                        count = Math.Min(termFreq1[termID].Count, termFreq2[termID].Count);
                    else if(termFreq1.ContainsKey(termID))
                        count = termFreq1[termID].Count;
                    else if(termFreq2.ContainsKey(termID))
                        count = termFreq2[termID].Count;
                    contributingTerms.Add(termID, count);
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);
            
            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)contributingTerms.Count * 2 /
                                 (termFreq1.Count + termFreq2.Count - contributingTerms.Count);
            cosineDistance = cosineDistance*docLenScale;

            //Dictionary<int,double> contributingTermWeights=new Dictionary<int, double>();
            //foreach (int sharedTermID in contributingTerms.Keys)
            //{
            //    if (termWeights.ContainsKey(sharedTermID))
            //    {
            //        contributingTermWeights.Add(sharedTermID, termWeights[sharedTermID]);
            //    }
            //    else
            //    {
            //        contributingTermWeights.Add(sharedTermID, 0);
            //    }
            //}
            double contributingTermScale =
                TermWeightCalculator.TaminoWeightedTermScale(
                    termFreq1, termFreq2,
                    termWeights);
            
            return cosineDistance * contributingTermScale;
        }

        public double CalculateDistance(Dictionary<int, DocTermFreq> termFreq1, Dictionary<int, DocTermFreq> termFreq2,
                                        Dictionary<int, double> termWeights, Dictionary<int, string> termStemmings1,
                                        Dictionary<string, List<int>> termStemmings2,
                                        ref Dictionary<int, int> contributingTerms)
        {
            Dictionary<int, double> unionTermIDs1 = new Dictionary<int, double>();
            Dictionary<int, double> unionTermIDs2 = new Dictionary<int, double>();
            foreach (int termID1 in termFreq1.Keys)
            {
                unionTermIDs1.Add(termID1, termFreq1[termID1].Weight * termFreq1[termID1].Count);
                if (termFreq2.ContainsKey(termID1))
                {
                    unionTermIDs2.Add(termID1, termFreq2[termID1].Count * termFreq2[termID1].Weight);
                }
                else
                {
                    unionTermIDs2.Add(termID1, 0);
                }
            }
            foreach (int termID2 in termFreq2.Keys)
            {
                if (!unionTermIDs2.ContainsKey(termID2))
                {
                    unionTermIDs2.Add(termID2, termFreq2[termID2].Count * termFreq2[termID2].Weight);
                }
                if (!unionTermIDs1.ContainsKey(termID2))
                {
                    unionTermIDs1.Add(termID2, 0);
                }
            }

            double length1 = 0;
            double length2 = 0;

            //sum of vector multiplication
            double svMul = 0;

            foreach (int termID in unionTermIDs1.Keys)
            {
                double freq1 = unionTermIDs1[termID];
                double freq2 = unionTermIDs2[termID];

                List<int> sharedTermIDs = termStemmings2[termStemmings1[termID]];
                List<int> sharedTermIDs1 = new List<int>();
                List<int> sharedTermIDs2 = new List<int>();
                foreach (int termID1 in termFreq1.Keys)
                {
                    if (sharedTermIDs.Contains(termID1))
                    {
                        sharedTermIDs1.Add(termID1);
                    }
                }
                foreach (int termID2 in termFreq2.Keys)
                {
                    if (sharedTermIDs.Contains(termID2))
                    {
                        sharedTermIDs2.Add(termID2);
                    }
                }
                if (sharedTermIDs1.Count>0 && sharedTermIDs2.Count>0)
                {
                    foreach (int similarTermID in sharedTermIDs1)
                    {
                        if (unionTermIDs1[similarTermID] > freq1)
                            freq1 = unionTermIDs1[similarTermID];
                    }
                    foreach (int similarTermID in sharedTermIDs2)
                    {
                        if (unionTermIDs2[similarTermID] > freq2)
                            freq2 = unionTermIDs2[similarTermID];
                    }
                }

                length1 += Math.Pow(freq1, 2);
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if (freq1 > 0 && freq2 > 0)
                {
                    int count = 0;
                    if (termFreq1.ContainsKey(termID) && termFreq2.ContainsKey(termID))
                        count = Math.Min(termFreq1[termID].Count, termFreq2[termID].Count);
                    else if (termFreq1.ContainsKey(termID))
                        count = termFreq1[termID].Count;
                    else if (termFreq2.ContainsKey(termID))
                        count = termFreq2[termID].Count;
                    contributingTerms.Add(termID, count);
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);

            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)contributingTerms.Count * 2 /
                                 (termFreq1.Count + termFreq2.Count - contributingTerms.Count);
            cosineDistance = cosineDistance * docLenScale;

            double contributingTermScale =
                TermWeightCalculator.TaminoWeightedTermScale(
                    termFreq1, termFreq2,
                    termWeights);

            return cosineDistance * contributingTermScale;
        }

        #endregion
    }
}
